﻿using System.Collections.Generic;

namespace FluentParser.Expressions.Rules.Composition
{
    /// <summary>
    /// Tries to read the same experssion multiple times. The resulting
    /// value is an array of the source type. (One-to-Many)
    /// </summary>
    /// <typeparam name="TSource">Type of the source and resulting value</typeparam>
    /// <example><code>
    /// var number = ParseNumber.Int32;
    /// var numberCollection = number.Repeat(",");
    /// 
    /// numberCollection.Parse("1,2,3,4") --> [1,2,3,4]
    /// numberCollection.Parse("1")       --> [1]
    /// numberCollection.Parse("abc")     --> failure
    /// </code></example>
    public class RepeatExpressionRule<TSource> : Expr<TSource[]> 
    {
        public Symbol Separator { get; private set; }
        public Expr<TSource> Source { get; private set; }
        public int Minimum { get; private set; }

        public RepeatExpressionRule(Expr<TSource> expr, int minimum, Symbol separator)
        {
            Source = expr;
            Minimum = minimum;
            Separator = separator;
            TypeOperation = OperationType.Repetition;
        }

        public override ParsingResult<TSource[]> Parse(GrammarContext context, int index)
        {
            var results = new List<TSource>();
            int endWithSeparator = index;
            int endWithoutSeparator = index;

            ParsingResult<TSource> result;
            while ((result = Source.Parse(context, endWithSeparator)) != null)
            {
                results.Add(result.Value);
                endWithSeparator = result.End;
                endWithoutSeparator = result.End;

                if (Separator != null)
                {
                    ParsingResult<object> separatorResult = Separator.Parse(context, endWithSeparator);
                    if (separatorResult == null)
                        break;
                    endWithSeparator = separatorResult.End;
                }
            }

            if (results.Count >= Minimum)
                return new ParsingResult<TSource[]>(index, endWithoutSeparator - index, results.ToArray());

            return null;
        }

        public override void ParseNode(GrammarContext context, ParsingNodeResult node)
        {
            List<TSource> resultingValues = new List<TSource>();

            ParsingNodeResult currentNode = node.AddChild(Source, context);

            while (currentNode.IsSuccess)
            {
                resultingValues.Add((TSource)currentNode.Value);

                if (Separator != null)
                {
                    int length = node.Length;
                    ParsingNodeResult separatorNode = node.AddChild(Separator, context, currentNode.End);
                    if (!separatorNode.IsSuccess)
                        break;

                    currentNode = node.AddChild(Source, context, separatorNode.End);
                    if (!currentNode.IsSuccess)
                    {
                        node.Length = length;
                        break;
                    }
                }
                else
                {
                    currentNode = node.AddChild(Source, context, currentNode.End);
                }
            }

            if (resultingValues.Count >= Minimum)
            {
                node.IsSuccess = true;
                node.Value = resultingValues.ToArray();
            }
            else
            {
                node.Length = 0;
            }
        }
    }
}